#![allow(unused_imports)]

use std::io::{Read} ;
use std::net::{SocketAddr, IpAddr};
use std::collections::{HashSet,HashMap} ;
use std::env::var ;
use std::{
        pin::Pin,
        task::{Context, Poll},
};


use dotenv::dotenv;

use tonic::{Request, Response, Status} ;
use tonic::transport::{Channel} ;
use signature::signature_server::{Signature, SignatureServer} ;
use signature::{PasswordTokenCreate, RefreshTokenCreate, ExchangeTokenCreate, SignatureTokenReply, SignatureToken, TrustTokenCreate} ;

use tonic_health::server::HealthReporter;

use self::utils::* ;

use std::convert::Infallible;
use futures::future::{self, Either, TryFutureExt};
use http::version::Version;
use hyper::{service::make_service_fn, Server};
use tower::Service;
use warp::{http::StatusCode, Filter, Reply};

type Error = Box<dyn std::error::Error + Send + Sync + 'static>;

pub mod signature {                                                                                                                                    
    tonic::include_proto!("proto.signature") ;
}

#[derive(Default)]
pub struct MySignature {}

#[tonic::async_trait]
impl Signature for MySignature { 
    async fn create_password_token(&self, create: Request<PasswordTokenCreate>) -> Result<Response<SignatureTokenReply>, Status> {
        handlers::create_password_token(create.into_inner())
    }
    async fn create_refresh_token(&self, create: Request<RefreshTokenCreate>) -> Result<Response<SignatureTokenReply>, Status> {
        handlers::create_refresh_token(create.into_inner())
    }
    async fn create_exchange_token(&self, _create: Request<ExchangeTokenCreate>) -> Result<Response<SignatureTokenReply>, Status> {
        unimplemented!()
        
    }

    async fn create_trust_token(&self, create: Request<TrustTokenCreate>) -> Result<Response<SignatureTokenReply>, Status> {
        handlers::create_trust_token(create.into_inner())
    }
}

mod states {
    use std::collections::HashMap ;

    // seed : QEqPpHQmKPR7KXsinZxouVeXHchfsox4bbMiUqKq/+Q=
    // pubkey: 3oJdJiBvUAbMCyX+Fw4+ttfvWhH7bKSrIpFDbvj+qz8=
/*
    pub fn init_secretkeys() -> Vec<String> {
        let mut secretkeys = Vec::new() ;
        let statickey = String::from("QEqPpHQmKPR7KXsinZxouVeXHchfsox4bbMiUqKq/+Q=") ;
        for day in 1..365 {
            secretkeys.push(statickey.clone()) ;
        }
        secretkeys
    } 
*/
}

mod models {
    use serde::{Deserialize, Serialize} ;
    use serde_json::json ;


    #[derive(Deserialize, Serialize)]
    pub struct TokenPayload {
        pub uuid: String,
        pub app_id: String,
        pub encryptkey: String
    }
    #[derive(Deserialize, Serialize)]
    pub struct AccessToken {
        pub start_ts: u64,
        pub end_ts: u64,
        pub ip: String,
        pub payload_enc: String
    }

    #[derive(Deserialize, Serialize)]
    pub struct RefreshToken {
        pub uuid_sha256: String
    }
}
mod models_impl {
    use super::models ;
    use super::codecs ;
    use super::utils ;

    use std::string::ToString ;
    use ring::signature ;
    use std::collections::HashMap ;

    impl ToString for models::TokenPayload {
        fn to_string(&self) -> String {
            return [ ["uuid", &self.uuid].join("="),
                     ["app_id", &self.app_id].join("=")].join("&")
        }
    }
    impl ToString for models::AccessToken {
        fn to_string(&self) -> String {
            return [ ["start_ts", &self.start_ts.to_string()].join("="),
                     ["end_ts", &self.end_ts.to_string()].join("="),
                     ["ip", &self.ip.to_string()].join("="),
                     ["payload_enc", &self.payload_enc].join("=")].join("&") ;
        }
    }
    impl ToString for models::RefreshToken {
        fn to_string(&self) -> String {
            return self.uuid_sha256.clone() ;
        }
    }

    impl models::AccessToken {
        pub fn from_string(s: &str) -> Result<Self, String> {
            let access_token_m = s.split("&").map(|x| utils::tuple2(x, "=").unwrap()).collect::<HashMap<&str, &str>>() ;

            let start_ts_s = access_token_m.get("start_ts").ok_or("field start_ts not find!")? ;
            let end_ts_s = access_token_m.get("end_ts").ok_or("field end_ts not find!")? ;

            let start_ts = u64::from_str_radix(start_ts_s, 10)
                .map_err(|_| String::from("start_ts is not a valid numeric!"))? ;
            let end_ts = u64::from_str_radix(end_ts_s, 10)
                .map_err(|_| String::from("end_ts is not a valid numeric!"))? ;

            let ip = access_token_m.get("ip").ok_or("field ip not find!")?.to_string() ;
            let payload_enc = access_token_m.get("payload_enc").ok_or("field payload_enc not find!")?.to_string() ;
            Ok(models::AccessToken {start_ts, end_ts, ip, payload_enc})
        }
    }
    impl models::TokenPayload {
        pub fn encrypt(&self) -> String {
            let encryptkey = codecs::encryptkey(&self.encryptkey).unwrap() ;
            let mut uuid_vec = self.uuid.as_bytes().to_vec() ;
            codecs::encrypt_base64(encryptkey, &mut uuid_vec).unwrap()
        }
        pub fn derive_refresh_token(&self) -> models::RefreshToken {
              models::RefreshToken {uuid_sha256: codecs::sha256_base64(self.uuid.clone().as_bytes()) }
        }
    }
}
mod handlers {
    use super::* ;
    use super::models ;
    use super::models_impl ;
    use super::auths ;
    use super::codecs ;
    use super::utils ;


    use std::convert::Infallible;

    use serde_json::json;

    use itertools::Itertools;

    // pub fn create_password_token(token: super::PasswordTokenCreate) -> Result<super::SignatureTokenReply, String> {
    pub fn create_password_token(token: super::PasswordTokenCreate) -> Result<Response<SignatureTokenReply>, Status> {
        let seed = String::from("QEqPpHQmKPR7KXsinZxouVeXHchfsox4bbMiUqKq/+Q=") ;
        let pubkey =  String::from("3oJdJiBvUAbMCyX+Fw4+ttfvWhH7bKSrIpFDbvj+qz8=") ;
        let keypair = codecs::keypair(&seed, &pubkey).unwrap() ;

        // auths::password_auth(&token.app_id, &token.app_secret).map_err(|x| Status::unknown(x))? ;
        let app_id = token.app_id ;
        let uuid = codecs::uuid( app_id.as_bytes() ) ;
        
        let start_ts = utils::now_ts() ;
        
        let payload = models::TokenPayload {uuid: uuid.clone(), app_id, encryptkey: pubkey} ;
        let access_token = models::AccessToken {
            start_ts, end_ts: start_ts + 60 * 60, ip: token.ip, payload_enc: payload.encrypt() 
        } ;

        let access_token_s = access_token.to_string() ;
        let access_token_sign = codecs::sign(&keypair, access_token_s.as_bytes()) ;
        let token_result = super::SignatureToken {
            access_token : format!("sign={}&{}", access_token_sign, access_token_s),
            refresh_token: payload.derive_refresh_token().to_string(), 
        } ;
        Ok(Response::new(super::SignatureTokenReply { code: 0, msg: "success".to_string(), result: Some(token_result) }))
    }

    // pub fn create_refresh_token(token_create: super::RefreshTokenCreate) -> Result<super::SignatureTokenReply, String> {
    pub fn create_refresh_token(token_create: super::RefreshTokenCreate) -> Result<Response<SignatureTokenReply>, Status> {
        let seed = String::from("QEqPpHQmKPR7KXsinZxouVeXHchfsox4bbMiUqKq/+Q=") ;
        let pubkey_b64 = String::from("3oJdJiBvUAbMCyX+Fw4+ttfvWhH7bKSrIpFDbvj+qz8=") ;

        let keypair = codecs::keypair(&seed, &pubkey_b64).unwrap() ;
        let pubkey_bs = codecs::unbase64(&pubkey_b64).map_err(|x| Status::unknown(x))? ;
        let pubkey = codecs::pubkey(&pubkey_bs) ;

        let (sign, access_token_s) = utils::tuple2(&token_create.access_token, "&").ok_or(Status::invalid_argument("missing sign and data!"))? ;
        let (sign_name, sign_val) = utils::tuple2(&sign, "=").ok_or(Status::invalid_argument("key-value needed!"))? ;
        if sign_name != "sign" { Err(Status::invalid_argument("token sign is not first field!"))? ; } 

        codecs::sign_verify(&pubkey, access_token_s, sign_val).map_err(|x| Status::permission_denied(x))? ;

        let access_token = models::AccessToken::from_string(access_token_s).map_err(|x|Status::invalid_argument(x))? ;

        let now_ts = utils::now_ts() ;

        if access_token.end_ts < now_ts { Err(Status::deadline_exceeded("token expire!"))? ;}
        let next_access_token = models::AccessToken {
                start_ts: now_ts,
                end_ts: now_ts + 60 * 60,
                ip: token_create.ip,
                payload_enc: access_token.payload_enc
            } ;

        let next_access_token_s = next_access_token.to_string() ;
        let next_access_token_sign = codecs::sign(&keypair, next_access_token_s.as_bytes()) ;
        let token_result = super::SignatureToken {
            access_token : format!("sign={}&{}", next_access_token_sign, next_access_token_s),
            refresh_token: token_create.refresh_token
        } ;
        Ok( Response::new(super::SignatureTokenReply { code: 0, msg: "success".to_string(), result: Some(token_result) }))
    }

    pub fn create_trust_token(token_create: super::TrustTokenCreate) -> Result<Response<SignatureTokenReply>, Status> {
        let seed = String::from("QEqPpHQmKPR7KXsinZxouVeXHchfsox4bbMiUqKq/+Q=") ;
        let pubkey =  String::from("3oJdJiBvUAbMCyX+Fw4+ttfvWhH7bKSrIpFDbvj+qz8=") ;
        let keypair = codecs::keypair(&seed, &pubkey).unwrap() ;

        if token_create.key != pubkey {
            Err(Status::permission_denied("please check the key!"))?
        }

        let start_ts = utils::now_ts() ;
        
        let encryptkey = codecs::encryptkey(&pubkey).unwrap() ;
        let mut paylod_vec = format!("app_id={}", token_create.app_id).as_bytes().to_vec() ;
        let payload_enc = codecs::encrypt_base64(encryptkey, &mut paylod_vec).unwrap() ;

        let access_token = models::AccessToken {
            start_ts, end_ts: 253402185600, ip: "0.0.0.0".into(), payload_enc: payload_enc
        } ;

        let trust_access_token_s = access_token.to_string() ;
        let trust_access_token_sign = codecs::sign(&keypair, trust_access_token_s.as_bytes()) ;
        let token_result = super::SignatureToken {
            access_token : format!("sign={}&{}", trust_access_token_sign, trust_access_token_s),
            refresh_token: "".into()
        } ;

        Ok( Response::new(super::SignatureTokenReply { code: 0, msg: "success".to_string(), result: Some(token_result) }))
    }
}

mod auths {
    use super::codecs ;
    pub fn password_auth(app_id: &str, app_secret: &str) -> Result<(), String> {
        let hash = read_hash(app_id)? ;
        let hash_verify = codecs::hash_verify(&hash, app_secret.as_bytes())? ;
        if ! hash_verify { Err(String::from("password error!"))?; }
        Ok(())
    }
    pub fn read_hash(_app_id: &str) -> Result<String, String> {
        // if app_id == "chinapex" {
            let db_app_secret = codecs::hash(b"chinapex")? ;
            Ok(db_app_secret)
        // } else {
        //     Err("only chinapex account allowed!".to_string())
        // }
    }
}

mod codecs {
    use uuid::Uuid ;
    use data_encoding::BASE64;

    use ring::rand ;
    use ring::digest ;
    pub use ring::signature::{Signature, KeyPair, Ed25519KeyPair, ED25519, UnparsedPublicKey};
    use ring::aead ;

    use argon2 ;

    pub fn uuid(id: & [u8]) -> String {
        Uuid::new_v5(&Uuid::NAMESPACE_OID, id).to_string()
    }
    pub fn base64(s: &[u8]) -> String {
        BASE64.encode(s)
    }
    pub fn unbase64(s: &str) -> Result<Vec<u8>, String> {
        BASE64.decode(s.as_bytes()).map_err(|x| x.to_string())
    }
    pub fn sha256(s: &[u8]) -> Vec<u8> {
        digest::digest(&digest::SHA256, s).as_ref().to_vec()
    }
    pub fn sha256_base64(s: &[u8]) -> String {
        base64(sha256(s).as_ref())
    }

/*
    pub fn generate_keypair() -> Result<(String, String), String> {
        let rnd = rand::SystemRandom::new() ;
        let seed : [u8; 32] = rand::generate(& rnd).map_err(|_| "generate seed fail!")?.expose() ;
        let key_pair = Ed25519KeyPair::from_seed_unchecked(&seed).unwrap() ;
        let seed_b64 = base64(seed.as_ref()) ;
        let pubkey_b64 = base64(key_pair.public_key().as_ref()) ;
        Ok((seed_b64, pubkey_b64)) 
    }
*/
    pub fn keypair(seed_b64: &str, pubkey_b64: &str) -> Result<Ed25519KeyPair, String> {
        let seed = unbase64(seed_b64)? ;
        let pubkey_b64 = unbase64(pubkey_b64)? ;
        let key_pair = Ed25519KeyPair::from_seed_and_public_key(&seed, &pubkey_b64).unwrap() ;
        Ok(key_pair)
    }


    pub fn pubkey(pubkey: &[u8]) -> UnparsedPublicKey<&[u8]> {
        UnparsedPublicKey::new (&ED25519, &pubkey)
    }

    pub fn sign(keypair: &Ed25519KeyPair, msg: &[u8]) -> String {
        base64(keypair.sign(msg).as_ref())
    }
    pub fn sign_verify(pubkey: &UnparsedPublicKey<&[u8]>, msg: &str, sign_b64: &str) -> Result<(), String> {
        let sign = unbase64(sign_b64)? ;
        pubkey.verify(&msg.as_bytes(), &sign).map_err(|x| format!("{:?}", x))
    }

    pub fn encryptkey (bkey_b64: &str) -> Result<aead::LessSafeKey, String> {
        let bkey = unbase64(bkey_b64)? ;
        let ubkey = aead::UnboundKey::new(&aead::AES_256_GCM, &bkey)
            .map_err(|_| String::from("aes_256_gcm_key error!"))? ;
        Ok(aead::LessSafeKey::new(ubkey))
    }
    pub fn encrypt(key: aead::LessSafeKey, s: &mut Vec<u8>) -> Result<(), String> {
        let aad = aead::Aad::from(b"larluo") ;
        let nonce = aead::Nonce::try_assume_unique_for_key(b"larluolarluo")
            .map_err(|_| String::from("encrypt nonce error!"))? ;
        key.seal_in_place_append_tag(nonce, aad, s)
           .map_err(|_| String::from("aes_256_gcm encrypt error!"))
    }
    pub fn encrypt_base64(key: aead::LessSafeKey, s: &mut Vec<u8>) -> Result<String, String> {
        encrypt(key, s)? ;
        Ok(base64(s))
    }
/*
    pub fn descrypt(key: aead::LessSafeKey, s: &mut Vec<u8>) -> Result<&mut [u8], String> {
        let aad = aead::Aad::from(b"larluo") ;
        let nonce = aead::Nonce::try_assume_unique_for_key(b"larluolarluo")
            .map_err(|_| String::from("encrypt nonce error!"))? ;
        key.open_in_place(nonce, aad, s)
           .map_err(|_| String::from("aes_256_gcm descrypt error!"))
    }
*/

    pub fn hash(password: &[u8]) -> Result<String, String> {
        let salt = b"randomsalt" ;
        argon2::hash_encoded(password, salt, &argon2::Config::default())
            .map_err(|x| x.to_string() )
    }
    pub fn hash_verify(hash: &str, password: &[u8]) -> Result<bool, String> {
        argon2::verify_encoded(hash, password)
            .map_err(|x| x.to_string() )
    }
}
mod utils {
    use super::* ;
    use std::time::{SystemTime, UNIX_EPOCH} ;
    use std::collections::HashMap;

    use itertools::Itertools;

    pub fn now_ts() -> u64 {
        SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()
    }

    pub fn tuple2<'a>(s: &'a str, sep: &str) -> Option<(&'a str, &'a str)> {
        s.splitn(2, sep).next_tuple::<(_, _)>()
    }

    pub fn resolve_socket_addr(nodes: &str) -> Option<IpAddr> {
        let ips = get_if_addrs::get_if_addrs().unwrap().iter()
            .map(|interface| interface.ip()).collect::<HashSet<_>>();
        nodes.split(",")
            .filter_map(|x| x.parse::<IpAddr>().ok())
            .filter(|x| ips.contains(&x))
            .nth(0)
    }
}

enum EitherBody<A, B> {
    Left(A),
    Right(B),
}

impl<A, B> http_body::Body for EitherBody<A, B>
where
    A: http_body::Body + Send + Unpin,
    B: http_body::Body<Data = A::Data> + Send + Unpin,
    A::Error: Into<Error>,
    B::Error: Into<Error>,
{
    type Data = A::Data;
    type Error = Box<dyn std::error::Error + Send + Sync + 'static>;

    fn is_end_stream(&self) -> bool {
        match self {
            EitherBody::Left(b) => b.is_end_stream(),
            EitherBody::Right(b) => b.is_end_stream(),
        }
    }

    fn poll_data(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
    ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
        match self.get_mut() {
            EitherBody::Left(b) => Pin::new(b).poll_data(cx).map(map_option_err),
            EitherBody::Right(b) => Pin::new(b).poll_data(cx).map(map_option_err),
        }
    }

    fn poll_trailers(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
    ) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
        match self.get_mut() {
            EitherBody::Left(b) => Pin::new(b).poll_trailers(cx).map_err(Into::into),
            EitherBody::Right(b) => Pin::new(b).poll_trailers(cx).map_err(Into::into),
        }
    }
}

fn map_option_err<T, U: Into<Error>>(err: Option<Result<T, U>>) -> Option<Result<T, Error>> {
    err.map(|e| e.map_err(Into::into))
}


#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    dotenv::from_filename("meta-lib-static/env/.env").ok();

    let hosts = var("SIGNATURE_SERVER_HOSTS").unwrap_or("127.0.0.1".to_string()) ;
    let port = var("SIGNATURE_SERVER_PORT").unwrap().parse::<u16>().unwrap() ;

    let o_ip = resolve_socket_addr(&hosts) ;
    if o_ip.is_none() {
        println!("SIGNATURE_SERVER_NODES not match! skip") ;
        return Ok(()) ;
    }
    let addr = SocketAddr::new(o_ip.unwrap(), port) ;
    println!("start server at {}", addr) ;


/*
    let (mut health_reporter, health_service) = tonic_health::server::health_reporter();
    health_reporter.set_serving::<SignatureServer<MySignature>>().await ;
    Server::builder()
        .add_service(health_service)
        .add_service(SignatureServer::new(MySignature::default()))
        .serve(addr)
        .await? ;
*/
    let my_tonic = SignatureServer::new(MySignature::default()) ;
    let my_warp = warp::service(warp::path("signature").and(warp::path("health").map(|| StatusCode::OK))) ;

    Server::bind(&addr)
        .serve(make_service_fn(move |_| {
            let mut my_tonic = my_tonic.clone();
            let mut my_warp = my_warp.clone();
            future::ok::<_, Infallible>(tower::service_fn(
                move |req: hyper::Request<hyper::Body>| match req.version() {
                    Version::HTTP_11 | Version::HTTP_10 => Either::Left(
                        my_warp
                            .call(req)
                            .map_ok(|res| res.map(EitherBody::Left))
                            .map_err(Error::from),
                    ),
                    Version::HTTP_2 => Either::Right(
                        my_tonic
                            .call(req)
                            .map_ok(|res| res.map(EitherBody::Right))
                            .map_err(Error::from),
                    ),
                    _ => unimplemented!(),
                },
            ))
        }))
        .await?;


    Ok(())
}

